﻿using System;
using System.Collections.Generic;
using System.Linq;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;
using Microsoft.Xna.Framework.Net;
using Microsoft.Xna.Framework.Storage;

namespace jogoLudumdare
{
    class Character
    {
        public Texture2D texture; //Textura do personagem.
        public Rectangle rectanglePicture; //Retângulo na tela in-game.
        public Rectangle rectangleSprites; //Retângulo nos sprites, define qual frame aparecerá na tela.
        private int delayAnimation; //Atrazo entre um frame e outro.
        private int countAnimation; //Contador para passagem de frame.
        private Vector2 speed; //Velocidade do personagem.
        public Color[] mapColor; //Array que armazena cores.
        private enum DirectionWalk //Direções possíveis do personagem andar.
        {
            stand, //Parado.
            right, //Direita.
            left //Esquerda.
        }
        private DirectionWalk directionWalk; //Direção que o personagem vai andar.
        public enum DirectionFace //Direções possíveis do personagem está virado.
        {
            right, //Direita.
            left //Esquerda.
        }
        public DirectionFace directionFace; //Direção que o personagem vai estar virado.
        private enum Action //Ações possíveis do personagem desempenhar.
        {
            stand, //Parado.
            jump, //Pular.
            down //Abaixar.
        }
        private Action action; //Ação desempenhada pelo personagem atualmente.
        public enum CombatState //Estados relacionados ao combate.
        {
            stand,
            shot,
            damage
        }
        public CombatState combatState; //Estado de combate atual.
        public Vector2 gravity; //Gravidade atuante no personagem.
        private int jumpForce; //Força potencial de pulo do personagem.
        public bool jumpAble; //Booliana que define se o personagem está apto a pular ou não
        public int life; //Vida do personagem.
        private int maxlife; //Vida máxima do personagem.
        public List<Bullet> bulletlistRight; //Instanciamos uma lista que utiliza bullets.
        public List<Bullet> bulletlistLeft; //Instanciamos uma lista que utiliza bullets.

        //Dentro do construtor igualamos os parâmetros aos atributos que serão iniciados com o objeto.
        public Character(Texture2D texture, Rectangle rectanglePicture, Rectangle rectangleSprites,
            Vector2 speed, int delayAnimation, int maxlife)
        {
            this.texture = texture;
            this.rectanglePicture = rectanglePicture;
            this.rectangleSprites = rectangleSprites;
            this.speed = speed;
            this.delayAnimation = delayAnimation;
            this.maxlife = maxlife;
            this.life = maxlife;
            //O mapa de cor irá armazenar a cor de todos os pixels da tetura, porisso ele deve ter o tamanho de toda a imagem.
            this.mapColor = new Color[this.texture.Width * this.texture.Height];
            this.texture.GetData(this.mapColor); //A textura é copiada para o array atravez do getdata.
            this.bulletlistRight = new List<Bullet>(); //Carregamos a lista de projeteis para a direita.
            this.bulletlistLeft = new List<Bullet>(); //Carregamos a lista de projeteis para a esquerda.
        }

        //Metodo de movimento. Obs: ajustar de acordo com o sprite.
        public void Movement(int WIDTH, int HEIGHT, Enemy inimigo, KeyboardState keyNow)
        {
            //Se for possível pular, então sua ação será de descanço.
            if (jumpAble)
                this.action = Action.stand;
            //Se o inimigo estiver a direita do personagem, então o personagem estará virado para a direita.
            if (inimigo.rectanglePicture.Left > this.rectanglePicture.Left)
                this.directionFace = DirectionFace.right;
            //Se o inimigo estiver a esquerda do personagem, então o personagem estará virado para a esquerda.
            if (inimigo.rectanglePicture.Right < this.rectanglePicture.Right)
                this.directionFace = DirectionFace.left;
            //Se o personagem for colidir com o inimigo.
            if (this.rectanglePicture.Right > inimigo.rectanglePicture.Left + 30 &&
                this.rectanglePicture.Left < inimigo.rectanglePicture.Right - 30 &&
                this.rectanglePicture.Bottom > inimigo.rectanglePicture.Top &&
                this.rectanglePicture.Bottom < inimigo.rectanglePicture.Bottom)
            {
                //Se ele estiver mais para a direita.
                if (this.rectanglePicture.Right > inimigo.rectanglePicture.Right && this.rectanglePicture.Left > inimigo.rectanglePicture.Left)
                    this.rectanglePicture.X += (int)this.speed.X; //Ele segue para a direita.
                //Se ele estiver mais para a esquerda.
                if (this.rectanglePicture.Right < inimigo.rectanglePicture.Right && this.rectanglePicture.Left < inimigo.rectanglePicture.Left)
                    this.rectanglePicture.X -= (int)this.speed.X; //Ele segue para a esquerda.
            }

            #region Entrada de teclas

            //Se a tecla (seta direita) estiver pressionada, a direção de andar será a direita.
            if (keyNow.IsKeyDown(Keys.Right))
                this.directionWalk = DirectionWalk.right;

            //Se a tecla (seta direita) não estiver sendo pressionada e se a direção de andar atual seja direita, a direção de andar será de descanso.
            if (keyNow.IsKeyUp(Keys.Right) && this.directionWalk == DirectionWalk.right)
                this.directionWalk = DirectionWalk.stand;

            //Se a tecla (seta esquerda) estiver pressionada, a direção de andar será a esquerda.
            if (keyNow.IsKeyDown(Keys.Left))
                this.directionWalk = DirectionWalk.left;

            //Se a tecla (seta esquerda) não estiver sendo pressionada e se a direção de andar atual seja esquerda, a direção de andar será de descanso.
            if (keyNow.IsKeyUp(Keys.Left) && this.directionWalk == DirectionWalk.left)
                this.directionWalk = DirectionWalk.stand;

            /*Se a tecla (seta cima) estiver sendo pressionada, e o usuário estiver apto a pular,
            e a força de pulo for igual a velocidade vertical do personagem, entao a ação que deve ser desempenhada é a de pular.*/
            if (keyNow.IsKeyDown(Keys.Up) && jumpAble && jumpForce == (int)this.speed.Y)
                this.action = Action.jump;

            //Se a tecla (seta baixo) estiver sendo pressionada, e a ação atual for de descanço, a ação que deve ser desempenhada é a de abaixar.
            if (keyNow.IsKeyDown(Keys.Down) && this.action == Action.stand)
                this.action = Action.down;

            /*Se a tecla (seta cima) e (seta baixo) não estiverem sendo pressionada, e for possível pular,
            a ação que deve ser desempenhada é a de descanso.*/
            if (keyNow.IsKeyUp(Keys.Up) && keyNow.IsKeyUp(Keys.Down) && jumpAble)
                this.action = Action.stand;

            //Se a tecla (Z) estiver sendo pressionada, o estado de combate é de soco.
            if (keyNow.IsKeyDown(Keys.Z))
                this.combatState = CombatState.shot;

            //Se a tecla (Z) não estiver sendo pressionada, o estado de combate é de descanço.
            if (keyNow.IsKeyUp(Keys.Z) && this.combatState == CombatState.shot)
                this.combatState = CombatState.stand;

            #endregion

            #region Andar

            //Código responssável pelo andar do personagem.
            switch (this.directionWalk)
            {
                //No caso da direção do andar ser direita.
                case DirectionWalk.right:
                    //Se a posição X do personagem na tela mais a largura do mesmo for menor que a largura da janela.
                    if (this.rectanglePicture.X + this.rectanglePicture.Width < WIDTH)
                    {
                        //Se o personagem não estiver colidindo com a parte esquerda do inimigo.
                        if (this.rectanglePicture.Right < inimigo.rectanglePicture.Left + 30 ||
                            this.rectanglePicture.Bottom < inimigo.rectanglePicture.Top ||
                            this.rectanglePicture.Top > inimigo.rectanglePicture.Top ||
                            this.rectanglePicture.Left > inimigo.rectanglePicture.Right - 40)
                        {
                            //Se o personagem não estiver abaixado.
                            if (this.action != Action.down)
                                //Então aumentamos a posição X do personagem pela velocidade do mesmo.
                                this.rectanglePicture.X += (int)this.speed.X;
                        }
                    }
                    if (this.combatState == CombatState.shot)
                    {
                        this.rectangleSprites.X = 535;
                        this.rectangleSprites.Y = 0;
                    }
                    else if (this.action == Action.stand)
                    {
                        //Se a contagem da animação for maior que 0, então diminuiremos ela em 1.
                        if (this.countAnimation > 0)
                            this.countAnimation--;
                        else //Senão...
                        {
                            //Colocamos o retângulo de sprites na posição Y 0 (posição dos sprites de corrida).
                            this.rectangleSprites.Y = 0;
                            //Se o retângulo nos sprites estiver numa posição menor que 360 (posição em que os sprites acabam).
                            if (this.rectangleSprites.X < 360)
                                this.rectangleSprites.X += 180; //Aumentamos a posição X do retângulo de sprites em 180 (tamanho de cada frame).
                            //Senão nós definimos a posição X do retângulo de sprites como 0.
                            else
                                this.rectangleSprites.X = 0;
                            //Por fim igualamos a contagem de frames à variável de daley definida por nós, assim a animação irá se repetir com o atrazo controlado.
                            this.countAnimation = delayAnimation;
                        }
                    }
                    break;
                //No caso da direção do andar ser esquerda.
                case DirectionWalk.left:
                    //Se a posição X do personagem for maior que 0.
                    if (this.rectanglePicture.X > 0)
                    {
                        //Se o personagem não estiver colidindo com a parte direita do inimigo.
                        if (this.rectanglePicture.Left > inimigo.rectanglePicture.Right - 30 ||
                            this.rectanglePicture.Bottom < inimigo.rectanglePicture.Top ||
                            this.rectanglePicture.Top > inimigo.rectanglePicture.Top ||
                            this.rectanglePicture.Right < inimigo.rectanglePicture.Left + 40)
                        {
                            //Se o personagem não estiver abaixado.
                            if (this.action != Action.down)
                                //Então diminuimos a posição X do personagem pela velocidade do mesmo.
                                this.rectanglePicture.X -= (int)this.speed.X;
                        }
                    }
                    if (this.combatState == CombatState.shot)
                    {
                        this.rectangleSprites.X = 535;
                        this.rectangleSprites.Y = 0;
                    }
                    if (this.action == Action.stand)
                    {
                        //Se a contagem da animação for maior que 0, então diminuiremos ela em 1.
                        if (this.countAnimation > 0)
                            this.countAnimation--;
                        else //Senão...
                        {
                            //Colocamos o retângulo de sprites na posição Y 0 (posição dos sprites de corrida).
                            this.rectangleSprites.Y = 0;
                            //Se o retângulo nos sprites estiver numa posição menor que 360 (posição em que os sprites acabam).
                            if (this.rectangleSprites.X < 360)
                                this.rectangleSprites.X += 180; //Aumentamos a posição X do retângulo de sprites em 170 (tamanho de cada frame).
                            //Senão nós definimos a posição X do retângulo de sprites como 0.
                            else
                                this.rectangleSprites.X = 0;
                            //Por fim igualamos a contagem de frames à variável de delay definida por nós, assim a animação irá se repetir com o atrazo controlado.
                            this.countAnimation = delayAnimation;
                        }
                    }
                    break;
                //No caso da direção do andar ser de descanso.
                case DirectionWalk.stand:
                    //Se a ação for de descanço, definimos a posição do retângulo de sprites sendo 0, 0 (frame em que o personagem está parado).
                    if (this.combatState == CombatState.shot)
                    {
                        this.rectangleSprites.X = 535;
                        this.rectangleSprites.Y = 0;
                    }
                    else if (this.action == Action.stand)
                    {
                        this.rectangleSprites.X = 0;
                        this.rectangleSprites.Y = 0;
                    }
                    break;
            }

            #endregion

            #region Ações

            //Código responssável pelas ações do personagem.
            switch (this.action)
            {
                //No caso da ação desempenhada ser a de pulo.
                case Action.jump:
                    //Diminuimos a posição Y do personagem pela força de pulo do mesmo.
                    this.rectanglePicture.Y -= this.jumpForce;
                    if (this.jumpForce > 0) //Se a força de pulo do personagem for maior que 0, diminuimos ela em 1.
                        this.jumpForce--;
                    //Se a contagem da animação for maior que 0, então diminuiremos ela em 1.
                    if (this.countAnimation > 0)
                        this.countAnimation--;
                    else //Senão...
                    {
                        if (this.combatState == CombatState.shot)
                        {
                            this.rectangleSprites.Y = 220;
                            this.rectangleSprites.X = 535;
                        }
                        else
                        {
                            this.rectangleSprites.Y = 220;
                            this.rectangleSprites.X = 350;
                        }
                        //Por fim igualamos a contagem de frames à variável de delay definida por nós, assim a animação irá se repetir com o atrazo controlado.
                        this.countAnimation = delayAnimation;
                    }
                    break;
                //No caso da ação desempenhada ser a de abaixar.
                case Action.down:
                    //Definimos o retangulo de sprites na posição X,Y (sprite dele abaixado).
                    if (this.combatState == CombatState.shot)
                    {
                        this.rectangleSprites.X = 175;
                        this.rectangleSprites.Y = 220;
                    }
                    else
                    {
                        this.rectangleSprites.X = 0;
                        this.rectangleSprites.Y = 220;
                    }
                    break;
                //No caso da ação desempenhada ser a de descanso.
                case Action.stand:
                    //Igualamos a força de pulo do personagem à velocidade Y do mesmo (definida por nós quando instanciamos a classe).
                    this.jumpForce = (int)this.speed.Y;
                    break;
            }

            #endregion

        }

        //Metodo de tiro.
        public void Shot(Bullet bullet, KeyboardState keyNow)
        {
            if (Bullet.shotAbleCleber)
            {
                //Se a tecla (Z) estiver pressionada.
                if (keyNow.IsKeyDown(Keys.Z))
                {
                    //Código que depende da direção que o personagem estiver virado.
                    switch (this.directionFace)
                    {
                        //No caso da direção que o personagem estiver virado for direita.
                        case DirectionFace.right:
                            //Adicionar mais um tiro.
                            if (this.action != Action.down)
                                //Obs:Ajustar de acordo com o sprite.
                                this.bulletlistRight.Add(bullet = new Bullet(bullet.texture, new Rectangle(this.rectanglePicture.X + this.rectanglePicture.Width, this.rectanglePicture.Y + 40, 20, 10)));
                            else
                                //Obs:Ajustar de acordo com o sprite.
                                this.bulletlistRight.Add(bullet = new Bullet(bullet.texture, new Rectangle(this.rectanglePicture.X + this.rectanglePicture.Width, this.rectanglePicture.Y + 50, 20, 10)));
                            break;
                        //No caso da direção que o fabiardo deve estar virado for esquerda.
                        case DirectionFace.left:
                            //Adicionar mais um tiro.
                            if (this.action != Action.down)
                                //Obs:Ajustar de acordo com o sprite.
                                this.bulletlistLeft.Add(bullet = new Bullet(bullet.texture, new Rectangle(this.rectanglePicture.X, this.rectanglePicture.Y + 40, 20, 10)));
                            else
                                //Obs:Ajustar de acordo com o sprite.
                                this.bulletlistLeft.Add(bullet = new Bullet(bullet.texture, new Rectangle(this.rectanglePicture.X, this.rectanglePicture.Y + 50, 20, 10)));
                            break;
                    }
                    Bullet.shotAbleCleber = false; //Por fim será impossivel atirar.
                }
            }
            //Se a tecla (Z) não estiver sendo pressionada.
            if (keyNow.IsKeyUp(Keys.Z))
                Bullet.shotAbleCleber = true; //O personagem está apto a atirar novamente.
            //Atualizar a posicao de cada tiro.
            foreach (Bullet n in bulletlistRight)
                n.rectanglePicture.X += 10;
            //Atualizar a posicao de cada tiro.
            foreach (Bullet n in bulletlistLeft)
                n.rectanglePicture.X -= 10;
        }

        //Metodo de remoção de tiros.
        public void RemoveBullets(Background bg_limiteDireita, Background bg_limiteEsquerda)
        {
            for (int x = 0; x < this.bulletlistRight.Count; x++)
            {
                //Se o tiro sair da fase, nós removemos ele da memoria.
                if (this.bulletlistRight[x].rectanglePicture.X > bg_limiteDireita.rectanglePicture.X + bg_limiteDireita.rectanglePicture.Width)
                    this.bulletlistRight.RemoveAt(x);
            }
            for (int x = 0; x < this.bulletlistLeft.Count; x++)
            {
                //Se o tiro sair da fase, nós removemos ele da memoria.
                if (this.bulletlistLeft[x].rectanglePicture.X < bg_limiteEsquerda.rectanglePicture.X)
                    this.bulletlistLeft.RemoveAt(x);
            }
        }

        /*Metodo de colisão por pixel, ele determina se houve sobreposição de pixels não-transparentes.
         * Obs: os parâmetros são o retângulo e o mapa de cores do objeto que o inimigo deve colidir.*/
        public bool Colision(Rectangle retangulo, Color[] corObjeto2)
        {
            //Côdigo responssável por encontrar os lados dos retângulos de inserseção.
            int up = Math.Max(this.rectanglePicture.Top, retangulo.Top);
            int down = Math.Min(this.rectanglePicture.Bottom, retangulo.Bottom);
            int left = Math.Max(this.rectanglePicture.Left, retangulo.Left);
            int right = Math.Min(this.rectanglePicture.Right, retangulo.Right);

            for (int y = up; y < down; y++) //Loop para verificar cada linha da imagem (de cima pra baixo).
            {
                for (int x = left; x < right; x++) //Loop para verificar cada coluna da imagem (da esquerda pra direita).
                {
                    //Aqui pegamos cada pixel de cada imagem que será usada na colisão.
                    Color color1 = this.mapColor[(x - this.rectanglePicture.Left) + (y - this.rectanglePicture.Top)
                        * this.rectanglePicture.Width];
                    Color color2 = corObjeto2[(x - retangulo.Left) + (y - retangulo.Top) * retangulo.Width];

                    if (color1.A != 0 && color2.A != 0) //Se ambos os pixels nao forem transparentes.
                        return true; //Então retorna verdadeiro, pois uma interseção foi encontrada.
                }
            }
            return false; //retorna falso, pois não foi encontrada interseção alguma.
        }

        //Metodo que define os limites do objeto para colisão.
        public BoundingBox GetBounding()
        {
            return new BoundingBox(new Vector3(this.rectanglePicture.X,
                    this.rectanglePicture.Y, 0.0f),
                new Vector3(this.rectanglePicture.X + this.rectanglePicture.Width,
                    this.rectanglePicture.Y + this.rectanglePicture.Height, 0.0f));
        }
    }
}
